﻿using System;
using System.Net.Sockets;
using System.Threading;
using System.Timers;
using InTheHand.Net;
using InTheHand.Net.Bluetooth;
using InTheHand.Net.Sockets;

namespace CommLib
{
    public interface IBtCallback
    {
        void onNoDevice();
        void onDisabled();
        void onNoPairedDevices();
        void onMessageAlive();
        void onConnectionClosed();
        void onReconnected();
        void onMessageBattery(int percent);
        void onMessageSignal(int percent);
        void onMessageContacts(Contact[] c);
        void onMessageConversations(Conversation[] c);
        void onMessageConversation(Conversation c);
        void onMessageSms(Sms s);
        void onMessageSmsList(int threadId, Sms[] s);
        void onMessageCalendar(int calendarId, CalendarEntry[] c);
        void onMessageCalendars(AndroidCalendar[] c);
        void onMessageSmsSent(bool success);
        void onMessageSmsDelivered(bool success);
        void onMessageCallLog(CallLogType type, CallLogEntry[] c);
    }

    public class BtConnection : ICommCallback
    {
        private readonly static Guid UUID = new Guid("6ebd20b0-e12a-11e1-9b23-0800200c9a66");

        private readonly IBtCallback mCallback;
        private readonly BluetoothDeviceInfo[] mDevices;
        private readonly bool mCanConnect;
        private readonly System.Timers.Timer mTimer;

        private volatile bool mDisconnectRequested;
        private volatile bool mConnected;
        private volatile bool mAutoReconnect;
        private volatile int mReconnectInterval;
        private volatile BluetoothAddress mAddress;

        private CommThread mCommThread;

        public BluetoothDeviceInfo[] PairedDevices
        { get { return mDevices; } }
        public bool AutoReconnect
        { 
            get { return mAutoReconnect; }
            set { mAutoReconnect = value; } 
        }
        public int ReconnectInterval
        {
            get { return mReconnectInterval; }
            set { mReconnectInterval = value; }
        }
        public bool IsConnected
        { get { return mConnected; } }

        public BtConnection(IBtCallback callback)
        {
            mConnected = false;
            mCanConnect = false;
            mTimer = new System.Timers.Timer();

            if (callback == null)
                return;

            mCallback = callback;
            BluetoothRadio br = BluetoothRadio.PrimaryRadio;

            if (!BluetoothRadio.IsSupported || br == null)
            {
                mCallback.onNoDevice();
                return;
            }

            if (br.LocalAddress == null)
            {
                mCallback.onDisabled();
                return;
            }

            BluetoothClient client = new BluetoothClient();
            client.Encrypt = true;
            client.Authenticate = true;
            mDevices = client.DiscoverDevices(255, true, true, false, false);

            if (mDevices == null || mDevices.Length == 0)
            {
                mCallback.onNoPairedDevices();
                return;
            }

            mCanConnect = true;
            // AutoReconnect alle 1 Minuten
            AutoReconnect = true;
            ReconnectInterval = 60000;
            mTimer.AutoReset = false;
            mTimer.Elapsed += new ElapsedEventHandler(TimerElapsed);
        }

        ~BtConnection()
        {
            disconnect();
        }

        public static BluetoothDeviceInfo[] getPairedDevices()
        {
            BluetoothClient client = new BluetoothClient();
            return client.DiscoverDevices(255, true, true, false, false);
        }

        public bool connect(BluetoothAddress address)
        {
            mAddress = address;
            
            disconnect();

            if (mCanConnect && mAddress != null)
            {
                mDisconnectRequested = false;

                // Auf disconnect warten
                if (mCommThread != null)
                {
                    while (mCommThread.isRunning())
                    {
                        Thread.Sleep(1);
                    }
                }

                try
                {
                    BluetoothClient client = new BluetoothClient();
                    client.Encrypt = true;
                    client.Authenticate = true;
                    client.Connect(mAddress, UUID);

                    // Kommunikations Thread starten
                    mCommThread = new CommThread(client.GetStream(), this);
                    Thread workerThread = new Thread(mCommThread.loop);
                    workerThread.Start();

                    mConnected = true;

                    return true;
                }
                catch (SocketException e)
                {
                    Console.WriteLine(e.Message);

                    if (AutoReconnect)
                    { 
                        // Timer starten
                        mTimer.Interval = ReconnectInterval;
                        mTimer.Start();
                    }
                }
            }

            return false;
        }

        public void disconnect()
        {
            mDisconnectRequested = true;
            mConnected = false;

            // AutoReconnect Timer stoppen
            mTimer.Stop();

            if (mCommThread != null)
            {
                mCommThread.stop();
            }
        }

        public void requestConversationContent(int threadId)
        {
            if (mCommThread != null)
                mCommThread.offerMessage(new MessageRequest(RequestType.SmsList, threadId));
        }

        public void requestContacts()
        {
            if (mCommThread != null)
                mCommThread.offerMessage(new MessageRequest(RequestType.ContactsList));
        }

        public void requestConversations()
        {
            if (mCommThread != null)
                mCommThread.offerMessage(new MessageRequest(RequestType.ThreadList));
        }

        public void requestCalendarEntries(int id)
        {
            if (mCommThread != null)
                mCommThread.offerMessage(new MessageRequest(RequestType.Calendar, id));
        }

        public void requestCalendars()
        {
            if (mCommThread != null)
                mCommThread.offerMessage(new MessageRequest(RequestType.CalendarList));
        }

        public void requestCallLog(CallLogType type)
        {
            if (mCommThread != null)
                mCommThread.offerMessage(new MessageRequest(RequestType.CallLog, (int)type));
        }

        public bool sendSms(string number, string text)
        {
            if (mCommThread != null && mConnected
                && number != null && text != null 
                && !number.Equals("") && !text.Equals(""))
            {
                Sms sms = new Sms(text, DateTime.Now, SmsType.Draft, -1, number, SmsStatus.Sending);
                mCommThread.offerMessage(new MessageSms(sms));
                return true;
            }
            
            return false;
        }

        private void TimerElapsed(object sender, ElapsedEventArgs e)
        {
            if (connect(mAddress))
                mCallback.onReconnected();
        }

        #region ICommCallback Member

        public void onConnectionClosed()
        {
            mConnected = false;

            // Wenn die Verbindung absichtlich getrennt wurde, muss keine Benachrichtigung erfolgen
            // Auch kein Reconnect nötig
            if (!mDisconnectRequested)
            {
                mCallback.onConnectionClosed();

                // AutoReconnect
                if (AutoReconnect)
                {
                    if (connect(mAddress))
                        mCallback.onReconnected();
                }
            }
        }

        public void onMessageAlive()
        {
            mCallback.onMessageAlive();
        }

        public void onMessageBattery(MessageBattery msg)
        {
            int percent = (int)((float)msg.getLevel() / (float)msg.getScale() * 100);
            mCallback.onMessageBattery(percent);
        }

        public void onMessageSignal(MessageSignal msg)
        {
            int percent = (int)((float)msg.getStrength() / (float)MessageSignal.MAX_STRENGTH * 100);
            mCallback.onMessageSignal(percent);
        }

        public void onMessageContacts(MessageContacts msg)
        {
            mCallback.onMessageContacts(msg.getContacts());
        }

        public void onMessageConversations(MessageConversations msg)
        {
            mCallback.onMessageConversations(msg.getConversations());
        }

        public void onMessageSms(MessageSms msg)
        {
            Sms sms = msg.getSms();
            mCallback.onMessageSms(sms);

            // Konversationsdetails anfordern
            //if (mCommThread != null)
                //mCommThread.offerMessage(new MessageRequest(RequestType.Conversation, sms.getThreadId()));
        }

        public void onMessageSmsList(MessageSmsList msg)
        {
            mCallback.onMessageSmsList(msg.getThreadId(), msg.getSmsList());
        }

        public void onMessageSmsReceived()
        {
            // Sms anfragen
            //if (mCommThread != null)
                //mCommThread.offerMessage(new MessageRequest(RequestType.SmsLatest));
        }

        public void onMessageSmsSent(int statusCode)
        {
            mCallback.onMessageSmsSent(statusCode == MessageStatus.SMS_SENT_SUCCESS);
        }

        public void onMessageSmsDelivered(int statusCode)
        {
            mCallback.onMessageSmsDelivered(statusCode == MessageStatus.SMS_DELIVERED_SUCCESS);
        }

        public void onMessageConversation(MessageConversation msg)
        {
            mCallback.onMessageConversation(msg.getConversation());
        }

        public void onMessageCalendar(MessageCalendar msg)
        {
            mCallback.onMessageCalendar(msg.getCalendarId(), msg.getEntries());
        }

        public void onMessageCalendars(MessageCalendars msg)
        {
            mCallback.onMessageCalendars(msg.getCalendars());
        }

        public void onMessageCallLog(MessageCallLog msg)
        {
            mCallback.onMessageCallLog(msg.getCallLogType(), msg.getEntries());
        }

        #endregion
    }

}
